﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Collections;

using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

using ElfDocHelpers;

namespace ElfDoc
{

    public class DocumentConvolution
    {

        public string Source { get; set; }
        public string Target { get; set; }

        public void Convolve<T>(T model) where T : class 
        {

            // copy template word document file to another file, that will be poked with data
            File.Copy( this.Source , this.Target ) ;

            // a list of all content controls.
            var allContentControlList = GetListOfAllContentControls();

            // a list content controls that have alias-titles, some may have blank alias-title &/or blank tags.
            var contentControlAliasTitleList = GetListOfAllContentControlsByAliasNames(allContentControlList);

            // content controls with distinct alias-titles
            var contentControlAliasTitleDistinctList = contentControlAliasTitleList.Distinct().ToList();

            // content controls that are meant for single property access ; not 'iterative' content controls ; i.e. this that do not have [] in the alias-title
            var singlePropertyContentControlAliasTitleListDistinct = contentControlAliasTitleDistinctList.Where(x => !x.Contains("[]")).ToList();

            // content controls that are meant for iterative property access ; 'iterative' content controls ; i.e. this that do have [] in the alias-title
            var iterativePropertyContentControlAliasTitleListDistinct = contentControlAliasTitleDistinctList.Where(x => x.Contains("[]")).ToList();

            // Poke values into the new document
            using (var wordprocessingDocument = WordprocessingDocument.Open(this.Target, true))
            {

                // some content-controls are there twice - we remember the unique id when we poke values to that control
                List<int> contentControlsDone = new List<int>();

                // for each content-control with an alias-title which is not an iterative content-control
                foreach (var modelPropertyName in singlePropertyContentControlAliasTitleListDistinct)
                {

                    // for each content control in the list of ALL content controls - poke data from the model to the new document
                    var contentControlList = wordprocessingDocument.MainDocumentPart.Document.Descendants<SdtElement>().ToList();

                    // for those with an aliasTitle property that is not an iterative control
                    foreach (var contentControl in contentControlList)
                    {

                        // try to get an alias-title property ; but it may not have that property
                        var aliasTitle = contentControl.GetAlias();
                        var controlID = contentControl.GetControlID();

                        // move data from the model.property to the document ; get the value of the property from the model object and get it's type
                        var modelValueTuple = model.ValueByKeyTuple(aliasTitle);

                        //
                        if (modelValueTuple.ObjectValue == null)
                        {
                            modelValueTuple.StringValue = String.Empty ;
                            modelValueTuple.ObjectValue = String.Empty ;
                        }

                        //
                        if (modelValueTuple.ObjectValue != null )
                        {
                            if (isPropertyControl(aliasTitle))
                            {
                                bool shouldPoke = false;
                                if (!(contentControlsDone.Any(x => x == controlID)))
                                {
                                    shouldPoke = true;
                                }

                                // remember which content-controls we have poked values to - we do not want to do this again ...
                                contentControlsDone.Add(controlID);

                                // if values from the model have not been poked to the document before then poke values
                                if (shouldPoke)
                                {

                                    if (!aliasTitle.EndsWith("List"))
                                    {
                                        // poke value to content control ; this control is not an iterative control
                                        contentControl.PokeValueToContentControl(modelValueTuple.StringValue );

                                    }
                                    else if (aliasTitle.EndsWith("List"))
                                    {

                                        // get the list that we are going to iterate over ; object list = model.ValueByKey(aliasTitle);
                                        IList modelList = (IList)model.ValueByKey(aliasTitle);

                                        // look for a table inside the contentControl - that table may have rows with content controls 
                                        // look for the first table that is a descendant of the content control.
                                        var wordProcessingTable = contentControl.Descendants<Table>().FirstOrDefault();

                                        // get the repeating row of the word processing document table ; the row of the word table that has content controls in it
                                        var theRepeatingTableRow = GetRepeatingRow(wordProcessingTable);

                                        // if there is a repeating row, then try to poke values according to the name of it's content controls
                                        if (theRepeatingTableRow != null)
                                        {
                                            if (modelList != null)
                                            {
                                                foreach (var modelItem in modelList)
                                                {

                                                    // get a clone of the row ;
                                                    var newTableRow = (TableRow)theRepeatingTableRow.CloneNode(true);

                                                    // get any content controls from the new table row ;
                                                    var contentControlOfTheNewTableRowList = newTableRow.Descendants<SdtElement>().ToList();

                                                    // if the new table row has content controls, then we try to poke data from the a property of the model that is a list
                                                    if (contentControlOfTheNewTableRowList != null && contentControlOfTheNewTableRowList.Count() > 0)
                                                    {

                                                        // for each content control of the new row, 
                                                        // get the alias-title 'name' of the content control 
                                                        // using the alias-title name of the content control, 
                                                        // get some data from the list that we are iterating around (modelItem of modelList)
                                                        // and poke it to the iterative content control
                                                        foreach (var contentControlOfRepeatingRow in contentControlOfTheNewTableRowList)
                                                        {

                                                            // get the alias-title of the content control inside the repeating row
                                                            var iterativeContentControlAliasTitle = contentControlOfRepeatingRow.GetAlias();

                                                            // get the value of the property, using the key value that is after the [] 
                                                            modelValueTuple = modelItem.ValueByKeyTuple(iterativeContentControlAliasTitle.TailLastAfter("[].")); 

                                                            // poke value to content control that is inside the new table row
                                                            contentControlOfRepeatingRow.PokeValueToContentControl(modelValueTuple.StringValue);
                                                        }
                                                    }

                                                    // insert the new row after the template for the repeating row
                                                    wordProcessingTable.InsertBefore(newTableRow, theRepeatingTableRow);

                                                }

                                                // remove template repeating row 
                                                wordProcessingTable.RemoveChild(theRepeatingTableRow);

                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        public static bool isPropertyControl(string contentControlName)
        {
            if (!String.IsNullOrEmpty(contentControlName))
            {
                // is property control iff contains "[]" ; 
                // i.e. this means if the square brackets does contain a valid index then it will be property control and not an iterative control [2]
                return !(contentControlName.Contains("[]"));
            }
            else
            {
                return false;
            }
        }

        public static TableRow GetRepeatingRow( Table tableContentControl )
        {
            //-Declaration

            // the 'repeating' row of the first table
            TableRow theRepeatingRow = null;

            //-Code

            //-Get the first row of the first table, that contains content controls ...

            // get the rows of the table just inside the content control
            var tableRows = tableContentControl.Elements<TableRow>().ToList();

            // move over each row of the first of the table, check whether it contains content controls ...
            foreach (var row in tableRows)
            {

                // get any content controls in this row, this is checking all descendants, so ....
                // this row may have a nested table, with content controls
                var rowHasContentControls = row.Descendants<SdtElement>().ToList();

                // does this row of the first table have content controls, if it does then this is the repeating row
                if (rowHasContentControls != null && rowHasContentControls.Count() > 0)
                {
                    theRepeatingRow = row;
                }

            }

            // return the repeating row of the first table
            return theRepeatingRow;

        }

        public List<SdtElement> GetListOfAllContentControls()
        {

            //-Declaration

            // the return value of this function
            List<SdtElement> contentControlList;

            // open the file from disc and get it's list of document templates
            using (var wordprocessingDocument = WordprocessingDocument.Open( this.Source, true ) )
            {
                contentControlList = wordprocessingDocument
                                        .MainDocumentPart
                                        .Document
                                        .Descendants<SdtElement>()
                                        .ToList();
            }

            //-Return
            return contentControlList;

        }

        public List<String> GetListOfAllContentControlsByAliasNames(List<SdtElement> contentControlList)
        {

            //-Declaration

            List<string> nameList = new List<string>();

            //-Code

            //
            foreach (var cc in contentControlList)
            {

                var prop = cc.SdtProperties.GetFirstChild<SdtAlias>();

                if (prop != null)
                {
                    string aliasTitle = cc.SdtProperties.GetFirstChild<SdtAlias>().Val.Value;
                    if (aliasTitle != null)
                    {
                        nameList.Add(aliasTitle);
                    }
                }
            }

            //-Return
            return nameList;

        }

    }

}

